Introducción a R
Introducción a R
- Resumen
- Qué es, de dónde y cómo instalo R?
- Tutorial: Introducción a R
- Variables (Global environment)
- Las matrices en R
- Creación de matrices en R
- Indexación de matrices en R
- Llamando sólo una fila de una matriz
- Llamando sólo una columna de una matriz
- Combinando formas de indexar
- Los data.frame
- Construcción de
data.frames - Agregando datos al
data.frame - Indexación de
data.frames - Las listas
- Extracción de los elementos de la lista
- Extrayendo elementos contenidos en una lista de listas
- Agregando nuevos elementos a una lista
- Leyendo datos en R
- Estructuras de Control y Loops
- Funciones definidas por el usuario.
- La operaciones vectorizadas
- Usando una funcón que llama c
purrrprogramación funcional
Resumen
En el presente documento se da una breve introducción al lenguaje R. Exploraremos desde las funciones más básicas del lenguaje (como leer tablas de datos, hacer aritmética, algunas operaciones algebraicas) hasta cómo utilizar funciones de paquetes especializados para el análisis espacial (i.e. raster, rgdal, dismo). También se hará énfansis en cómo programar funciones propias o funciones definidas por el usuario (helper functions) las cuales tienen el objetivo de facilitarnos tareas complejas. Es importante notar que operaciones básicas como la indexación suelen ser vitales para poder realizar operaciones mucho más complicadas y complejas dentro de los loops y estructuras de control.
Qué es, de dónde y cómo instalo R?
R es un lenguaje de programación de código abierto el cual está basado en el lenguaje S. En sus inicios el lenguaje realizaba solo operaciones estadísticas y gráficas, sin embargo a través de los años se ha convertido en un lenguaje polifacético en donde podemos encontrar desde paquetes para el análisis de textos (text mining), web scraping, análisis espaciales (i.e. spatstat) hasta para la programación de aplicaciones web (i.e shiny). Lo anterior se debe a que varios de sus usuarios se han convertido en desarrolladores activos de la comunidad. Acutualmente existen casi 13000 paquetes (Fig. 1), el crecimiento de estos ha sido exponencial (Fig. 2).
Figura 1. Número de paquetes en CRAN
Figura 2. Serie de tiempo del número de paquetes en CRAN
Obtención e instalación de R
R se puede descargar de la siguiente página web http://www.r-project.org/.
En el panel izquierdo encontrarás la opción de download donde elegirás un mirror para descargarlo, los links para México son los siguientes:
- https://cran.itam.mx/
- http://cran.itam.mx/
- http://www.est.colpos.mx/R-mirror/
- http://cran.jellyfish.lol/
Elige el link de descarga que te parezca más adecuado en función del área en que te encuentres. Una vez elegido el mirror, selecciona la versión de R de acuerdo a tu sistema operativo (Linux, Mac o Windows).
Figura_4
La forma más fácil de instalar R es por medio de los binarios (o ejecutables), sin embargo si deseas instalarlo desde teminal de Linux o Mac aquí puedes encontrar las instrucciones:
- MAC https://rud.is/b/2015/10/22/installing-r-on-os-x-100-homebrew-edition/
- Linux (Ubuntu) https://cran.r-project.org/bin/linux/ubuntu/README
Antes de empezar con el tutorial voy a fijar una semilla aleatoria para que los ejemplos de este documento sean reproducibles (es decir, para que todos en el grupo lleguen a los mismos resultados).
Tutorial: Introducción a R
Una vez que han instalado R en sus computadoras ahora estamos listos para comenzar a aprender programación en este ambiente maravilloso.
Sintaxis
La primera cosa que hay que explorar es la sintaxis del lenguaje; en esta sección daremos un panorama general de ella. La primera cosa que hay que tener en cuenta sobre la sintaxis de R es que a diferencia de otros lenguajes como SQL, BASIC, Pascal, R es sensible a mayúsculas y minúsculas por lo que un objeto llamado libro es diferente a Libro. Veamos el ejemplo:
libro <- "En las montañas de la locura"
# Existe en la memoria de R el objeto libro?
exists("libro")## [1] TRUE
## [1] FALSE
A pesar de que no se comentó anteriormente (se dio por hecho), en R la forma de declarar variables es mediante el comando de asignación (<-). Aunque también es posible declarar las variables con el signo (=) se recomienda usar <-. Veremos más sobre variables en las siguientes secciones
# Asigna a uno el valor numérico 1 usando el comando "<-"
uno <- 1
# Alternativamente
1 -> uno
# Asigna a dos el valor numérico 2 usando el comando "="
dos = 2
# Asignar el valor 3 a la variable 3 usando el comando assing
assign("tres", 3)
print(uno)## [1] 1
## [1] 2
## [1] 3
Otra característica de la sintaxis es que en R solo se permite nombrar a las variables comenzando con letras (mayúsculas o minúsculas) o “.”* y nunca con números.
Olor <- "Dulce"
olor <- "Agradable"
# Esto produce un error
# 0lor <- "Fétido" # la variable comienza con el digito cero
print(Olor)## [1] "Dulce"
## [1] "Agradable"
Las variables pueden contener números en cualquier otra posición (nunca al comienzo).
## [1] "fresco"
## [1] "fresco"
Ahora probemos con el “.”*
## [1] "SOS"
R como calculadora
La consola de R puede ser usada como calculadora aritmética, vemos algunas operaciones básicas:
Una suma
## [1] 94
Una resta
## [1] -149
Una multiplicación
## [1] 196
División
## [1] 1.2
Potencia
## [1] 387420489
El residuo de la división
## [1] 2
La parte entera
## [1] 1
Podemos verificar que 9 es la suma de su residuo más 7 veces la parte entera de la fracción
## [1] 9
Los paréntesis puede ser usados para especificar el orden de las operaciones
## [1] 2.713765
R contiene algunas funciones matemáticas ya incluidas como \(\sin(x)\), \(\cos(x)\), \(\tan(x)\), (en radianes), \(\exp(x)\), \(\log(x)\) y \(\sqrt{x}\).
## [1] 2.718282
## [1] 0.841471
## [1] 0.5403023
## [1] 0
## [1] 1
También tiene en memoria algunas constantes como \(\pi\)
## [1] 3.141593
Podemos especificar la precisión de las salidas con el comando options
## [1] 3.141592653589793
Redondear cantidades
## [1] 2.94
## [1] 1
## [1] 0
Variables (Global environment)
R tiene un ambiente de trabajo conocido como ambiente global (Global environment) en donde se guardan los resultados de los cálculos. Los resultados se guardan en forma de objetos o variables; una variable es como un folder etiquetado donde guardamos documentos, éstos puede ser de diversa naturaleza y siempre tenemos la opción de cambiar el contenido de ellos, sin embargo, el folder se llamará de la misma manera.
Las variables globales se encuentran en el Global environment y pueden ser llamadas en cualquier momento durante la sesión; así, si quisiéramos almacenar una cantidad, digamos la tasa de interés anual a la que pagaremos un coche, podemos guardarla en la variable i.e. tasa_anual y utilizarla cuando la necesitemos.
Imaginemos que vamos a comprar un Audi RS5 4.2 FSI 450 hp y que queremos pagarlo a 48 meses (4 años). La tasa anual que maneja audi es aproximadamente de 12.9%; olvidándonos de los pagos de comisión por apertura etc. hagamos el cálculo de cuánto pagaremos por nuestro Audi (Fig. 5).
Figura 5. Audi RS5 4.2 FSI 450 hp
# Auto Audi RS5 4.2 FSI 450 hp precio de lista
audi_rs5 <- 1484900.00
# Tasa anual de 12.9%
tasa_anual <- 0.129
# Cuanto pagaremos de interes en un año
(interes_anual <- audi_rs5*tasa_anual)## [1] 191552.1
## [1] 766208.4
## [1] 2251108.4
Los vectores y matrices en R
Comencemos con algunas definiciones necesarias:
- Escalar: Un escalar es sólo una cantidad numérica i.e. un entero o un número real.
- Vector \(v \in R^n\) : Se llama vector de dimensión \(n\) a una tupla de \(n\) números reales o componentes del vector (un vector está constituido por 1 o varios escalares).
El conjunto de todos los vectores de dimensión \(n\), se representa como \(\mathbb{R}^n\). Así, un vector \({\textbf v}\) perteneciente a un espacio \(\mathbb{R}^n\) se representa como:
\({\textbf v} = (a_1, a_2, a_3, \dots, a_n)\) donde \({\textbf v} \in \mathbb{R}^n\)
En matemáticas existen dos tipos de vectores, el vector renglón (el de la ecuación de arriba) y el vector columna.
Vector columna
\[\textbf v= \left( \begin{array}{c} a_1\\ a_2\\ \vdots\\ a_n\\ \end{array} \right)\]
Para saber más sobre las operaciones matemáticas de vectores y matrices en R da clic aquí.
Indexación de vectores en R
Nótese que se puede localizar a cada componente del vector por su posición (ver ejemplo).
\[\textbf f = (5,2,4,3)\]
De modo que la componente 3 del vector \(\textbf f\) es el número \(4\). En R, a lo anterior se le denomina indexación y es una de las herramientas más poderosas que podemos aprender; si logramos dominar este arte, la programación y automatización de tareas muy complejas será mucho más sencilla.
El desconocimiento de la forma en cómo se indexan los vectores o matrices hace que el código fuente de los programas o scripts sea obscuro. En los siguientes ejemplos veremos que esta arte es mucho más clara de lo que parece.
Primero debemos notar que en R la forma más básica de construir vectores es utilizando la función concatenar “c”. Este comando concatena a cada uno de los elementos que formarán a nuestro vector.
Comprobemos si efectivamente el elemento 3 del vector \({\textbf f}\) es el número \(4\)
## [1] 4
Efectivamente es el número 4 y que hay del primer elemento?
## [1] 5
Si quisiéramos extraer más de un elemento (un subconjunto \(\textbf f_1\) de dimensión \(m\) con \(m \le n\)) del vector \({\textbf f}\)?
Ahora se muestra como formar un subconjunto \(\textbf f_1\) constituido por los elementos 1, 3 y 4 del vector \({\textbf f}\)
## [1] 5 4 3
Nota: En algunos lenguajes como python la indexación comienza con el 0.
Generación de secuencias (vectores) regulares en R
R tiene varias formas de generar secuencias, por ejemplo, el comando 1:n genera la secuencia \(1,2,3,\dots,n\). La secuencia anterior es una de las más utilizadas para generar subconjuntos de vectores de dimensión \(n\) y cuyos elementos son números enteros.
## [1] 1 2 3 4 5 6 7 8 9 10
Otro comando que encontraremos con frecuencia es el comando “seq”, este comando genera secuencias de números en el intervalo \([a,b]\) con particiones de tamaño \(\delta\).
Generemos una secuencia de números del 0 al 10 de 2 en 2, es decir \(\delta=2\)
## [1] 0 2 4 6 8 10
Ahora una secuencia de 0 a 1 con \(\delta=0.01\)
## [1] 0.00000000000000000 0.01000000000000000 0.02000000000000000
## [4] 0.03000000000000000 0.04000000000000000 0.05000000000000000
## [7] 0.06000000000000000 0.07000000000000001 0.08000000000000000
## [10] 0.09000000000000000 0.10000000000000001 0.11000000000000000
## [13] 0.12000000000000000 0.13000000000000000 0.14000000000000001
## [16] 0.14999999999999999 0.16000000000000000 0.17000000000000001
## [19] 0.17999999999999999 0.19000000000000000 0.20000000000000001
## [22] 0.20999999999999999 0.22000000000000000 0.23000000000000001
## [25] 0.23999999999999999 0.25000000000000000 0.26000000000000001
## [28] 0.27000000000000002 0.28000000000000003 0.28999999999999998
## [31] 0.29999999999999999 0.31000000000000000 0.32000000000000001
## [34] 0.33000000000000002 0.34000000000000002 0.35000000000000003
## [37] 0.35999999999999999 0.37000000000000000 0.38000000000000000
## [40] 0.39000000000000001 0.40000000000000002 0.41000000000000003
## [43] 0.41999999999999998 0.42999999999999999 0.44000000000000000
## [46] 0.45000000000000001 0.46000000000000002 0.47000000000000003
## [49] 0.47999999999999998 0.48999999999999999 0.50000000000000000
## [52] 0.51000000000000001 0.52000000000000002 0.53000000000000003
## [55] 0.54000000000000004 0.55000000000000004 0.56000000000000005
## [58] 0.57000000000000006 0.57999999999999996 0.58999999999999997
## [61] 0.59999999999999998 0.60999999999999999 0.62000000000000000
## [64] 0.63000000000000000 0.64000000000000001 0.65000000000000002
## [67] 0.66000000000000003 0.67000000000000004 0.68000000000000005
## [70] 0.69000000000000006 0.70000000000000007 0.70999999999999996
## [73] 0.71999999999999997 0.72999999999999998 0.73999999999999999
## [76] 0.75000000000000000 0.76000000000000001 0.77000000000000002
## [79] 0.78000000000000003 0.79000000000000004 0.80000000000000004
## [82] 0.81000000000000005 0.82000000000000006 0.83000000000000007
## [85] 0.83999999999999997 0.84999999999999998 0.85999999999999999
## [88] 0.87000000000000000 0.88000000000000000 0.89000000000000001
## [91] 0.90000000000000002 0.91000000000000003 0.92000000000000004
## [94] 0.93000000000000005 0.94000000000000006 0.95000000000000007
## [97] 0.95999999999999996 0.96999999999999997 0.97999999999999998
## [100] 0.98999999999999999 1.00000000000000000
Imaginemos que se quiere una secuencia de números definidos en el intervalo \((13,25)\) pero cuya longitud sea de 40 números. Uno de los argumentos del comando seq es length.out y con este se puede especificar la longitud de la secuencia de valores que queremos generar.
## [1] 13.00000000000000 13.30769230769231 13.61538461538461
## [4] 13.92307692307692 14.23076923076923 14.53846153846154
## [7] 14.84615384615385 15.15384615384615 15.46153846153846
## [10] 15.76923076923077 16.07692307692308 16.38461538461539
## [13] 16.69230769230769 17.00000000000000 17.30769230769231
## [16] 17.61538461538462 17.92307692307692 18.23076923076923
## [19] 18.53846153846154 18.84615384615385 19.15384615384615
## [22] 19.46153846153846 19.76923076923077 20.07692307692308
## [25] 20.38461538461539 20.69230769230769 21.00000000000000
## [28] 21.30769230769231 21.61538461538462 21.92307692307692
## [31] 22.23076923076923 22.53846153846154 22.84615384615385
## [34] 23.15384615384615 23.46153846153846 23.76923076923077
## [37] 24.07692307692308 24.38461538461539 24.69230769230769
## [40] 25.00000000000000
El comando rep
La función rep permite generar secuencias repetidas de vectores.
## [1] "A" "B" "A" "B"
El comando sample
Un comando frecuentemente utilizado para extraer una muestra aleatoria de un vector el comando sample. Algunos de sus argumentos son:
- El vector de donde se va a sacar la secuencia aleatoria (\(x\)).
- El número de elementos (size) que se van a extraer del vector \(x\).
- La muestra será obtenida con remplazo (replace)
En el siguiente ejemplo crearemos un vector de 1000 elementos y tomaremos una muestra aleatoria de 100. Veamos cómo hacerlo en R!
## [1] 593 726 370 514 377 417 11 529 429 93 551 584 67 47 155 440 169
## [18] 951 306 603 423 280 335 378 945 314 637 276 766 579 57 495 451 454
## [35] 348 689 113 755 618 774 616 316 609 889 550 351 995 816 598 963 965
## [52] 556 32 959 943 519 544 431 91 759 1 439 163 243 861 217 50 284
## [69] 994 979 817 619 422 934 585 410 248 909 88 724 111 810 121 368 80
## [86] 343 626 671 704 993 466 776 572 526 913 955 899 360 880 743
Midiendo la longitud de un vector
Supongamos que estamos creando un algoritmo donde en uno de los pasos se genera un vector \(\textbf l\) cuyo tamaño puede variar en función de que se cumpla una condición \(c\); imaginemos que estamos interesados en saber cuál es el número promedio de iteraciones que necesita nuestro algoritmo para cumplir la condición \(c\). Una forma de averiguar lo anterior es guardar en un vector n_iter el número de iteraciones que el algoritmo necesitó en cada una de las simulaciones que relizamos. La pregunta es cómo saber la longitud del vector \(\textbf l\): en R es muy sencillo, el comando para realizarlo es length
# logitudes posibles de nuestro vector l
tamanos <- 1:30
# tamaño de l
tamano_l <- sample(tamanos,size = 1)
l <- seq(1,5,length.out = tamano_l)
# La longitud de l
length(l)## [1] 18
Las matrices en R
- Matriz: Es un arreglo bidimensional de números donde cada elemento de la matriz \(\textbf M\) se puede identidficar por la fila \(i\) y la columna \(j\) a la que pertenece.
En R las matrices pueden ser de 2 tipos:
- Numérica
- Cadena
Lo anterior quiere decir que no podemos combinar números con caracteres. Si fuera este el caso, R internamente realiza una operación conocida como casteo cast y convierte a todos los elementos a caracteres (strings)
Creación de matrices en R
Las matrices en R se crean con el comando matrix y sólo necesitamos indicar los elementos que la conformarán (data), el número de filas (nrow) y columnas (ncol) que contendrá.
## [,1] [,2] [,3] [,4]
## [1,] 1 5 9 13
## [2,] 2 6 10 14
## [3,] 3 7 11 15
## [4,] 4 8 12 16
Como se había mencionado las matrices en R sólo pueden ser de dos tipos numéricas o de cadena de caracteres:
Tratemos de combinar números con letras
letras_b <- LETTERS[1:8]
numero_b <- 1:8
letras_numeros <- c(letras_b,numero_b)
mat_num_car <- matrix(letras_numeros,ncol=4,nrow = 4)
print(mat_num_car)## [,1] [,2] [,3] [,4]
## [1,] "A" "E" "1" "5"
## [2,] "B" "F" "2" "6"
## [3,] "C" "G" "3" "7"
## [4,] "D" "H" "4" "8"
Indexación de matrices en R
Sea \(\textbf M\) una matriz de \(6\times 8\) dimensiones. Supongamos que se nos pide extraer el elemento que corresponde a la fila 3 y al renglón 6. La forma de indexar matrices en R sigue la misma lógica que en matemáticas en donde \(\mathbf{M_{fila,columna}}\) en R se indexa como \(\mathbf{M[fila,columna]}\). Veamos el código!
dats <- seq(2,4,length.out = 48)
M <- matrix(dats,nrow = 6,ncol = 8,byrow = TRUE)
# Extraigo el elemento a_{3,6}
print(M[3,6])## [1] 2.893617021276595
También podemos usar la indexación para mostrar una sola fila de la matriz, una columna completa, los primeros \(m\) elementos de una fila o una columna, las primeras \(k\) columnas y las primeras \(r\) filas de la matriz…
Llamando sólo una fila de una matriz
A continuación se muestra como enlistar la segunda fila de la matriz \(\mathbf M\)
## [1] 2.340425531914894 2.382978723404255 2.425531914893617 2.468085106382979
## [5] 2.510638297872340 2.553191489361702 2.595744680851064 2.638297872340425
Llamando sólo una columna de una matriz
A continuación se muestra como enlistar la tercera columna de la matriz \(\mathbf M\)
## [1] 2.085106382978724 2.425531914893617 2.765957446808510 3.106382978723405
## [5] 3.446808510638298 3.787234042553191
Combinando formas de indexar
Con las operaciones vistas hasta el momento podemos realizar varias combinaciones para extraer elementos de una matriz. Veamos algunos ejemplos de lo anterior.
Dada la matriz \(\mathbf {M_{5,5}}\) extraer:
- las primeras 3 filas de las columnas 1 y 2
## [,1] [,2]
## [1,] 1 2
## [2,] 6 7
## [3,] 11 12
- Las filas de 1 a la 5 de las columas 1 y 5
## [,1] [,2]
## [1,] 1 5
## [2,] 6 10
## [3,] 11 15
## [4,] 16 20
## [5,] 21 25
Para saber más sobre matrices y sus operaciones en R da clic aquí.
Los data.frame
Al igual que las matrices, un data.frame es un arreglo bidimensional de reglones y columnas. A diferencia de las matrices este permite tener columnas de tipo numérico y de tipo caracter en él. Cada columna representa una variable (en el sentido estadístico).
Construcción de data.frames
La función data.frame permite contruir un data.frame a partir de vectores; cada vector representa una variable o columna en el data.frame. A continuación veremos cómo construir un data.frame cuyas variables son de tipo numérico y de tipo caracter.
Pollitos
Construiremos una tabla de datos (data.frame) que tenga información sobre 5 lotes de 15 pollitos a los cuales daremos un identificador a nivel individuo y a nivel lote y también llevaramos un registro de su peso. El peso inicial promedio de los pollitos es de \(10 \pm 2\) gramos
pollito_id <- 1:75
lote <- paste0("L",rep(1:5,each=15))
peso_inicial <- runif(75,min = 8,12)
pollitos <- data.frame(pollito_id,lote,peso_inicial)| pollito_id | lote | peso_inicial | |
|---|---|---|---|
| 1 | 1 | L1 | 8.544862350448966 |
| 2 | 2 | L1 | 11.758935921825469 |
| 3 | 3 | L1 | 8.705138908699155 |
| 21 | 21 | L2 | 10.723021856509149 |
| 22 | 22 | L2 | 11.703913740813732 |
| 23 | 23 | L2 | 10.534392120316625 |
| 31 | 31 | L3 | 10.468478028662503 |
| 32 | 32 | L3 | 10.320646803826094 |
| 33 | 33 | L3 | 11.767220824025571 |
| 51 | 51 | L4 | 9.056855553761125 |
| 52 | 52 | L4 | 9.055385123938322 |
| 53 | 53 | L4 | 8.361524064093828 |
| 71 | 71 | L5 | 10.047071536071599 |
| 72 | 72 | L5 | 10.666186263784766 |
| 73 | 73 | L5 | 8.106038851663470 |
Agregando datos al data.frame
Supóngase que a cada lote de pollitos se les alimenta con una dieta diferente durante 45 días, por lo tanto será necesario agregar la vairable dieta a nuestra tabla de datos.
También la variable peso después de los 45 días de engorde.
peso_d1 <- rnorm(mean = 2250, n = 15,sd = 1)
peso_d2 <- rnorm(mean = 2600, n = 15,sd = 1.4)
peso_d3 <- rnorm(mean = 1800, n = 15,sd = 3)
peso_d4 <- rnorm(mean = 2100, n = 15,sd = .6)
peso_d5 <- rnorm(mean = 2000, n = 15,sd = 1.7)
pesos_dietas <- c(peso_d1,peso_d2,peso_d3,peso_d4,peso_d5)
pollitos$peso_dieta <- pesos_dietas | pollito_id | lote | peso_inicial | Dieta | peso_dieta | |
|---|---|---|---|---|---|
| 1 | 1 | L1 | 8.544862350448966 | D1 | 2249.397271513513 |
| 2 | 2 | L1 | 11.758935921825469 | D1 | 2250.660069387382 |
| 3 | 3 | L1 | 8.705138908699155 | D1 | 2252.050749526114 |
| 21 | 21 | L2 | 10.723021856509149 | D2 | 2602.428160185903 |
| 22 | 22 | L2 | 11.703913740813732 | D2 | 2599.510209257107 |
| 23 | 23 | L2 | 10.534392120316625 | D2 | 2601.678859712361 |
| 31 | 31 | L3 | 10.468478028662503 | D3 | 1802.638324892332 |
| 32 | 32 | L3 | 10.320646803826094 | D3 | 1801.181974725185 |
| 33 | 33 | L3 | 11.767220824025571 | D3 | 1802.066776451425 |
| 51 | 51 | L4 | 9.056855553761125 | D4 | 2100.258924159411 |
| 52 | 52 | L4 | 9.055385123938322 | D4 | 2099.637606327696 |
| 53 | 53 | L4 | 8.361524064093828 | D4 | 2100.404668015637 |
| 71 | 71 | L5 | 10.047071536071599 | D5 | 1997.980726526860 |
| 72 | 72 | L5 | 10.666186263784766 | D5 | 2001.064923304782 |
| 73 | 73 | L5 | 8.106038851663470 | D5 | 2000.614799222425 |
Indexación de data.frames
La indexación de los data.frames es idéntica al de las matrices; sin embargo, tiene una ventaja adicional, esta ventaja radica en que podemos llamar una columana completa indicando el nombre de esta en vez del número. De este modo en el ejemplo de pollitos, no importa si no sabemos la posición exacta de la columna dieta R la encontrará por nosotros.
Veamos como hacerlo, imprimamos los primeros 10 elementos de la columana dieta
## [1] "D1" "D1" "D1" "D1" "D1" "D1" "D1" "D1" "D1" "D1"
Otra forma de realizar la operación anterior es utilizando el símbolo de \(\$\)
## [1] "D1" "D1" "D1" "D1" "D1" "D1" "D1" "D1" "D1" "D1"
Las listas
Es momento de introducir a uno de los objetos estrella de R, este es el objeto lista. Al igual que los vectores, las listas son arreglos unidimensionales con la peculiaridad de que los elementos que las integran pueden ser de varias clases, por ejemplo, data.frames, matrices, vectores, rasters, etc. A continuación veamos cómo crear una lista que contiene como elementos un data.frame, un vector y una lista, esta se crea con el comando list()
# un vector de numeros aleatorio del 0 a la 1
vector <- runif(10)
# Un subconjunto del DF pollitos
data_frame_pollito <- pollitos[1:10,]
# uan lista
sublista <- list(a=1:10)
lista <- list(vector=vector, pollitos_df = data_frame_pollito, sublista=sublista)
print(lista)## $vector
## [1] 0.8176385075785220 0.8938548327423632 0.6814558403566480
## [4] 0.4533687653020024 0.6515468196012080 0.7260923418216407
## [7] 0.6991440386045724 0.5504099403042346 0.8171223362442106
## [10] 0.2699587242677808
##
## $pollitos_df
## pollito_id lote peso_inicial Dieta peso_dieta
## 1 1 L1 8.544862350448966 D1 2249.397271513513
## 2 2 L1 11.758935921825469 D1 2250.660069387382
## 3 3 L1 8.705138908699155 D1 2252.050749526114
## 4 4 L1 11.278820903971791 D1 2250.490808178818
## 5 5 L1 8.048473510891199 D1 2248.268520581220
## 6 6 L1 10.560249282047153 D1 2250.710883659408
## 7 7 L1 9.068114688619971 D1 2250.013822911462
## 8 8 L1 10.277991003356874 D1 2248.598958402634
## 9 9 L1 11.861210129223764 D1 2251.259123665442
## 10 10 L1 8.793691774830222 D1 2249.872522481624
##
## $sublista
## $sublista$a
## [1] 1 2 3 4 5 6 7 8 9 10
Extracción de los elementos de la lista
Como habíamos visto en la sección anterior, las listas se comportan como vectores y por tanto la forma de extraer un elemento de esta simplemente es ocupando la sintaxis lista[[element_index]].
| pollito_id | lote | peso_inicial | Dieta | peso_dieta |
|---|---|---|---|---|
| 1 | L1 | 8.544862350448966 | D1 | 2249.397271513513 |
| 2 | L1 | 11.758935921825469 | D1 | 2250.660069387382 |
| 3 | L1 | 8.705138908699155 | D1 | 2252.050749526114 |
| 4 | L1 | 11.278820903971791 | D1 | 2250.490808178818 |
| 5 | L1 | 8.048473510891199 | D1 | 2248.268520581220 |
| 6 | L1 | 10.560249282047153 | D1 | 2250.710883659408 |
| 7 | L1 | 9.068114688619971 | D1 | 2250.013822911462 |
| 8 | L1 | 10.277991003356874 | D1 | 2248.598958402634 |
| 9 | L1 | 11.861210129223764 | D1 | 2251.259123665442 |
| 10 | L1 | 8.793691774830222 | D1 | 2249.872522481624 |
Otra forma de extraer un elemento de la lista es con la sintaxis lista$nombre_elemento
| pollito_id | lote | peso_inicial | Dieta | peso_dieta |
|---|---|---|---|---|
| 1 | L1 | 8.544862350448966 | D1 | 2249.397271513513 |
| 2 | L1 | 11.758935921825469 | D1 | 2250.660069387382 |
| 3 | L1 | 8.705138908699155 | D1 | 2252.050749526114 |
| 4 | L1 | 11.278820903971791 | D1 | 2250.490808178818 |
| 5 | L1 | 8.048473510891199 | D1 | 2248.268520581220 |
| 6 | L1 | 10.560249282047153 | D1 | 2250.710883659408 |
| 7 | L1 | 9.068114688619971 | D1 | 2250.013822911462 |
| 8 | L1 | 10.277991003356874 | D1 | 2248.598958402634 |
| 9 | L1 | 11.861210129223764 | D1 | 2251.259123665442 |
| 10 | L1 | 8.793691774830222 | D1 | 2249.872522481624 |
o bien lista[["nombre_elemento"]]
| pollito_id | lote | peso_inicial | Dieta | peso_dieta |
|---|---|---|---|---|
| 1 | L1 | 8.544862350448966 | D1 | 2249.397271513513 |
| 2 | L1 | 11.758935921825469 | D1 | 2250.660069387382 |
| 3 | L1 | 8.705138908699155 | D1 | 2252.050749526114 |
| 4 | L1 | 11.278820903971791 | D1 | 2250.490808178818 |
| 5 | L1 | 8.048473510891199 | D1 | 2248.268520581220 |
| 6 | L1 | 10.560249282047153 | D1 | 2250.710883659408 |
| 7 | L1 | 9.068114688619971 | D1 | 2250.013822911462 |
| 8 | L1 | 10.277991003356874 | D1 | 2248.598958402634 |
| 9 | L1 | 11.861210129223764 | D1 | 2251.259123665442 |
| 10 | L1 | 8.793691774830222 | D1 | 2249.872522481624 |
Extrayendo elementos contenidos en una lista de listas
Estamos interesados en extaer el elemento 10 del vector a contenido en la lista nombrada sublista que se encuentra dentro de nuestra lista principal. La sintaxis para extraer un elemento de una lista contenida dentro de otra lista es lista[[indice_nivel_1]][[indice_nivel_2]][[indice_nivel3]]
# El elemento de lista que a la sublista que contiene al vector objetivo es el 3
# Esta sublista esta contituida por un solo elemento el cual es el vector a
# El elemento del vector a que nos interesa es el 10
indice_nivel_1 <- 3
indice_nivel_2 <- 1
indice_nivel_3 <- 10
lista[[indice_nivel_1]][[indice_nivel_2]][[indice_nivel_3]]## [1] 10
o bien
## [1] 10
Agregando nuevos elementos a una lista
Agreguemos un nuevo elemento (un vector de números aleatorios) a lista
## $vector
## [1] 0.8176385075785220 0.8938548327423632 0.6814558403566480
## [4] 0.4533687653020024 0.6515468196012080 0.7260923418216407
## [7] 0.6991440386045724 0.5504099403042346 0.8171223362442106
## [10] 0.2699587242677808
##
## $pollitos_df
## pollito_id lote peso_inicial Dieta peso_dieta
## 1 1 L1 8.544862350448966 D1 2249.397271513513
## 2 2 L1 11.758935921825469 D1 2250.660069387382
## 3 3 L1 8.705138908699155 D1 2252.050749526114
## 4 4 L1 11.278820903971791 D1 2250.490808178818
## 5 5 L1 8.048473510891199 D1 2248.268520581220
## 6 6 L1 10.560249282047153 D1 2250.710883659408
## 7 7 L1 9.068114688619971 D1 2250.013822911462
## 8 8 L1 10.277991003356874 D1 2248.598958402634
## 9 9 L1 11.861210129223764 D1 2251.259123665442
## 10 10 L1 8.793691774830222 D1 2249.872522481624
##
## $sublista
## $sublista$a
## [1] 1 2 3 4 5 6 7 8 9 10
##
##
## $nuevo_elem
## [1] 1.69679092635824325 -0.88433743533715903 0.85648970146977699
## [4] 0.17494245057415270 -0.39850496200257007 0.27520256239410407
## [7] -1.26871879906914331 -0.01008132898571333 0.65340172450247769
## [10] 0.09857997949400066 0.90261694132495107 -0.51989749588921874
## [13] -1.29349097974465210 -0.04689771043879913 -0.63499626589402647
## [16] -0.21770227188875313 -1.57484166313679341 -0.27750315193029645
## [19] 0.55029220750056151 -0.61094661705016162 -2.13486545165556052
## [22] 0.69927743788382590 1.07719596695355735 0.86791815730251343
## [25] -0.79073937853598142 -0.10933017366165988 -0.93035006106079787
## [28] -0.36712126465207523 -0.89291430875059996 -0.35777280804279865
o bien
## $vector
## [1] 0.8176385075785220 0.8938548327423632 0.6814558403566480
## [4] 0.4533687653020024 0.6515468196012080 0.7260923418216407
## [7] 0.6991440386045724 0.5504099403042346 0.8171223362442106
## [10] 0.2699587242677808
##
## $pollitos_df
## pollito_id lote peso_inicial Dieta peso_dieta
## 1 1 L1 8.544862350448966 D1 2249.397271513513
## 2 2 L1 11.758935921825469 D1 2250.660069387382
## 3 3 L1 8.705138908699155 D1 2252.050749526114
## 4 4 L1 11.278820903971791 D1 2250.490808178818
## 5 5 L1 8.048473510891199 D1 2248.268520581220
## 6 6 L1 10.560249282047153 D1 2250.710883659408
## 7 7 L1 9.068114688619971 D1 2250.013822911462
## 8 8 L1 10.277991003356874 D1 2248.598958402634
## 9 9 L1 11.861210129223764 D1 2251.259123665442
## 10 10 L1 8.793691774830222 D1 2249.872522481624
##
## $sublista
## $sublista$a
## [1] 1 2 3 4 5 6 7 8 9 10
##
##
## $nuevo_elem
## [1] 1.69679092635824325 -0.88433743533715903 0.85648970146977699
## [4] 0.17494245057415270 -0.39850496200257007 0.27520256239410407
## [7] -1.26871879906914331 -0.01008132898571333 0.65340172450247769
## [10] 0.09857997949400066 0.90261694132495107 -0.51989749588921874
## [13] -1.29349097974465210 -0.04689771043879913 -0.63499626589402647
## [16] -0.21770227188875313 -1.57484166313679341 -0.27750315193029645
## [19] 0.55029220750056151 -0.61094661705016162 -2.13486545165556052
## [22] 0.69927743788382590 1.07719596695355735 0.86791815730251343
## [25] -0.79073937853598142 -0.10933017366165988 -0.93035006106079787
## [28] -0.36712126465207523 -0.89291430875059996 -0.35777280804279865
##
## $nuevo_elem_2
## [1] 0 0 0 1 0 0 0 0 1 0 0 0 0 1 1 1 1 1 0 0 1 0 1 1 0 0 1 1 1 1
Leyendo datos en R
Se pueden importar datos tabulados provenientes de varios formatos. Los más comunes son .csv,.txt y .xls. Los comandos para leer estos formatos son read.csv, read.table, read.xls (en el paquete gdata) y loadWorkbook (en el paquete XLConnect).
En este documento veremos cómo cargar datos con los comandos read.csv y read.table. El primero sirve para leer datos separados por comas y el segundo por tabulaciones.
Algunas bases de datos contienen otro tipo de separadores, tales como “;” “:” o cualquier otro caracter. Tanto read.csv como read.table tienen un argumento que perimite indicar el caracter sepador, este argumento es sep = "caracter". Así, si nuestra base de datos estuviera separada por “;”, sólo tendríamos que indicarlo de la siguiente manera: sep=";".
Veamos algunos ejemplos concretos.
- Datos separados por comas
| species | long | lat |
|---|---|---|
| bradypus_variegatus | -65.40000000000001 | -10.3833 |
| bradypus_variegatus | -65.38330000000001 | -10.3833 |
| bradypus_variegatus | -65.13330000000001 | -16.8000 |
| bradypus_variegatus | -63.66670000000000 | -17.4500 |
| bradypus_variegatus | -63.85000000000000 | -17.4000 |
| bradypus_variegatus | -64.41670000000001 | -16.0000 |
- Datos separados por tabulaciones
| name | longitude | latitude | issues |
|---|---|---|---|
| Ambystoma tigrinum | -92.48258000000000 | 34.46026 | cdround,gass84 |
| Ambystoma tigrinum | -95.88531999999999 | 31.93524 | cdround,gass84 |
| Ambystoma tigrinum | -93.27306000000000 | 45.21076 | cdround,gass84 |
| Ambystoma tigrinum | -96.32323000000000 | 44.00937 | cdround,gass84 |
| Ambystoma tigrinum | -95.60391000000000 | 41.46921 | cdround,gass84 |
| Ambystoma tigrinum | -97.75873000000000 | 35.26342 | cdround,gass84 |
- Usando
read.csvpara leer datos separados por tabulaciones
df_tab_to_csv <- read.csv("Arichivos_01Intro/ambys_data_dynamic_map.txt",header = T,sep=" ")
head(df_tab_to_csv[,1:4])| name | longitude | latitude | issues |
|---|---|---|---|
| Ambystoma tigrinum | -92.48258 | 34.46026 | cdround,gass84 |
| Ambystoma tigrinum | -95.88532 | 31.93524 | cdround,gass84 |
| Ambystoma tigrinum | -93.27306 | 45.21076 | cdround,gass84 |
| Ambystoma tigrinum | -96.32323 | 44.00937 | cdround,gass84 |
| Ambystoma tigrinum | -95.60391 | 41.46921 | cdround,gass84 |
| Ambystoma tigrinum | -97.75873 | 35.26342 | cdround,gass84 |
Nota: una cuestión que debemos de tomar en cuenta cuando leemos datos en R es la codificación de nuestro sistema, ya que esto puede causar que no podamos leer un archivo. Algunas de las codificaciones más frecuentes son: UTF-8 (default en MAC), ASCII, WINDOWS-1252 y las ISO-xxxx-x. En R podemos especificar la codificación con el argumento fileEncoding de las funciones read.csv y read.table
d <- read.csv("Arichivos_01Intro/ambys_data_dynamic_map.txt",header = T,sep=" ",fileEncoding = "UTF-8")
head(d[,5:7])| prov | key | datasetKey |
|---|---|---|
| gbif | 1265535984 | 50c9509d-22c7-4a22-a47d-8c48425ef4a7 |
| gbif | 1088947578 | 50c9509d-22c7-4a22-a47d-8c48425ef4a7 |
| gbif | 1088946504 | 50c9509d-22c7-4a22-a47d-8c48425ef4a7 |
| gbif | 1143544830 | 50c9509d-22c7-4a22-a47d-8c48425ef4a7 |
| gbif | 1227733876 | 50c9509d-22c7-4a22-a47d-8c48425ef4a7 |
| gbif | 1252601766 | 2e64dedd-0996-4cd6-b6cd-4f055a46c38c |
Un ejemplo de como leer archivos de Excel
Como se mencionó anteriormente, en R es posible leer archivos de Excel utilizando las funciones read.xls y loadWorkbook de los paquetes gdata y XLConnect respectivamente. Veamos cómo instalar estos paquetes para utilizar las funciones señaladas.
- Con
read.xlssólo tenemos que señalar la ruta donde se encuentra nuestro xls y la hoja del libro.
## gdata: read.xls support for 'XLS' (Excel 97-2004) files ENABLED.
##
## gdata: read.xls support for 'XLSX' (Excel 2007+) files ENABLED.
##
## Attaching package: 'gdata'
## The following object is masked from 'package:stats':
##
## nobs
## The following object is masked from 'package:utils':
##
## object.size
## The following object is masked from 'package:base':
##
## startsWith
| IterationNo | AUC_at_Value_0.95 | AUC_at_0.5 | AUC_ratio |
|---|---|---|---|
| 1 | 0.6904844465485830 | 0.455461834131189 | 1.51600945415264 |
| 2 | 0.6502447312374160 | 0.440165890604005 | 1.47727196749648 |
| 3 | 0.7395332992408410 | 0.466813435453584 | 1.58421596953881 |
| 4 | 0.6904844465485830 | 0.455461834131189 | 1.51600945415264 |
| 5 | 0.7908706395783081 | 0.479626956033633 | 1.64892867181312 |
| 6 | 0.6259150694167050 | 0.433288054415460 | 1.44457033384203 |
Veamos cómo hacer lo anterior con el paquete XLConnect
## Loading required package: XLConnectJars
## XLConnect 0.2-15 by Mirai Solutions GmbH [aut],
## Martin Studer [cre],
## The Apache Software Foundation [ctb, cph] (Apache POI),
## Graph Builder [ctb, cph] (Curvesapi Java library)
## http://www.mirai-solutions.com
## https://github.com/miraisolutions/xlconnect
lib_xls <- loadWorkbook("Arichivos_01Intro/data_Clean.xlsx")
# Mostrar las hojas dispobles
getSheets(lib_xls)## [1] "ROC" "OCCS"
| IterationNo | AUC_at_Value_0.95 | AUC_at_0.5 | AUC_ratio |
|---|---|---|---|
| 1 | 0.6904844465485830 | 0.455461834131189 | 1.51600945415264 |
| 2 | 0.6502447312374160 | 0.440165890604005 | 1.47727196749648 |
| 3 | 0.7395332992408410 | 0.466813435453584 | 1.58421596953881 |
| 4 | 0.6904844465485830 | 0.455461834131189 | 1.51600945415264 |
| 5 | 0.7908706395783081 | 0.479626956033633 | 1.64892867181312 |
| 6 | 0.6259150694167050 | 0.433288054415460 | 1.44457033384203 |
Estructuras de Control y Loops
Programar implica escribir instrucciones relativamente complejas y repetir de manera iterada algunas de ellas. Hay dos grandes tipos de programación:
La programación imperativa en donde se le indica a la computadora de manera consecutiva un conjunto de operaciones.
La programación declarativa en donde se da una descripción del resultado final (i.e HTML, \(\LaTeX\)) sin especificar cómo se obtiene tal resultado.
Dentro de cada uno de estos estilos generales hay subdivisiones y algunos programas pueden contener varios aspectos de ellos. De manera específica en R, podemos encontrar a la programación modular (paquetes), la orientada a objetos y funcional.
A continuación veremos unas estructuras conocidas como estructuras de control. Estas controlan cuántas veces una operación debe de repetirse.
La sentencia if
if es una sentencia cuya entrada es una operación de tipo lógica y nos permite probar si alguna condición se cumple o no. El diagrama de flujo para la sentencia if es el siguiente
La sintaxis if en R:
if (condicion) {
comandos o operaciones cuando TRUE
}
if (condicion) {
comandos o operaciones cuando TRUE
} else {
comandos o operaciones cuando FALSE
}Imaginemos que nos interesa hacer un conjunto de operaciones sólo sobre los números divisibles entre 2 (pares) y descartar aquellos números que no son pares, a continuación veremos cómo utilizar if para lograr nuestro cometido.
## 1 No es par
## 2 Es par
## 3 No es par
## 4 Es par
for loop
Supongamos que saber cuáles son los números que son pares en un vector de 100 elementos; esto implicaría repetir 100 veces el código de la sección anterior. La sentencia for permite especificar con tan solo una pocas líneas de código operaciones que se repiten n veces.
La sintaxis del for loop en R es
# Genero una secuencia de números enteros (del 150 al 550) aleatoria
vec_num <- sample(150:550,size = 100,replace = F)
# Nuestro vector es
print(vec_num)## [1] 533 414 331 330 523 345 429 327 508 353 283 442 360 254 387 479 297
## [18] 433 316 223 168 438 261 422 348 217 271 214 201 362 241 311 303 321
## [35] 402 401 439 493 545 478 542 340 437 383 299 259 371 302 430 260 507
## [52] 286 436 281 510 301 325 407 535 425 513 242 364 369 293 417 517 418
## [69] 235 494 249 547 380 519 204 412 416 243 323 421 245 454 165 188 239
## [86] 209 393 515 389 232 525 484 169 441 390 216 213 288 207 287
## 533 No es par
## 414 Es par
## 331 No es par
## 330 Es par
## 523 No es par
## 345 No es par
## 429 No es par
## 327 No es par
## 508 Es par
## 353 No es par
## 283 No es par
## 442 Es par
## 360 Es par
## 254 Es par
## 387 No es par
## 479 No es par
## 297 No es par
## 433 No es par
## 316 Es par
## 223 No es par
## 168 Es par
## 438 Es par
## 261 No es par
## 422 Es par
## 348 Es par
## 217 No es par
## 271 No es par
## 214 Es par
## 201 No es par
## 362 Es par
## 241 No es par
## 311 No es par
## 303 No es par
## 321 No es par
## 402 Es par
## 401 No es par
## 439 No es par
## 493 No es par
## 545 No es par
## 478 Es par
## 542 Es par
## 340 Es par
## 437 No es par
## 383 No es par
## 299 No es par
## 259 No es par
## 371 No es par
## 302 Es par
## 430 Es par
## 260 Es par
## 507 No es par
## 286 Es par
## 436 Es par
## 281 No es par
## 510 Es par
## 301 No es par
## 325 No es par
## 407 No es par
## 535 No es par
## 425 No es par
## 513 No es par
## 242 Es par
## 364 Es par
## 369 No es par
## 293 No es par
## 417 No es par
## 517 No es par
## 418 Es par
## 235 No es par
## 494 Es par
## 249 No es par
## 547 No es par
## 380 Es par
## 519 No es par
## 204 Es par
## 412 Es par
## 416 Es par
## 243 No es par
## 323 No es par
## 421 No es par
## 245 No es par
## 454 Es par
## 165 No es par
## 188 Es par
## 239 No es par
## 209 No es par
## 393 No es par
## 515 No es par
## 389 No es par
## 232 Es par
## 525 No es par
## 484 Es par
## 169 No es par
## 441 No es par
## 390 Es par
## 216 Es par
## 213 No es par
## 288 Es par
## 207 No es par
## 287 No es par
Alternativamente
# Iteramos sobre los elementos de vec_num
vec_size <- length(vec_num)
for(iterador in 1:vec_size){
n <- vec_num[iterador]
if(n %% 2 == 0)
cat(n,"Es par\n")
else
cat(n, "No es par\n")
}Ejemplos un poco más laboriosos
Supongan que ahora se les pide guardar en un vector los números que son pares y los que no lo son en otro (para números enteros son los impares).La idea para solucionar nuestro problema es simple, apliquemos el algoritmo par o impar y vayamos guardando (“rellenando”) los resultados en un vector nulo (vacío).
vec_pares <- NULL
vec_impares <- NULL
# Iteramos sobre los elementos de vec_num
vec_size <- length(vec_num)
for(iterador in 1:vec_size){
n <- vec_num[iterador]
if(n %% 2 == 0){
vec_pares[iterador] <- n
}
else{
vec_impares[iterador] <- n
}
}En el vector vec_pares se generaron de manera automática NAs (Not Aviable) cuando la condición es par no se cumplió (R lo hace de manera automática). Lo mismo ocurrió para el vector vec_impares cuando la otra condición no se cumplió (condición es impar).
Veamos cómo quitar los NAs.
## [1] 414 330 508 442 360 254 316 168 438 422 348 214 362 402 478 542 340
## [18] 302 430 260 286 436 510 242 364 418 494 380 204 412 416 454 188 232
## [35] 484 390 216 288
## [1] 533 331 523 345 429 327 353 283 387 479 297 433 223 261 217 271 201
## [18] 241 311 303 321 401 439 493 545 437 383 299 259 371 507 281 301 325
## [35] 407 535 425 513 369 293 417 517 235 249 547 519 243 323 421 245 165
## [52] 239 209 393 515 389 525 169 441 213 207 287
De forma similar podemos filtrar los datos con el comando is.na. Debido a que estamos interesados en los datos que no son NA usamos el comando de negación ! para preguntar cuáles no son NA. Finalmente, con el comando which podemos saber las posiciones del vector vec_pares que no son NA.
vec_pares_limpio <- vec_pares[which(!is.na(vec_pares))]
vec_impares_limpio <- vec_impares[which(!is.na(vec_impares))]Si nos preguntaramos de nuestro vector original (vec_num) cuantos son pares y cuantos son impares…
## El numero de enteros pares en vec_num fue de 38
## El numero de enteros impares en vec_num fue de 62
while loop
Habrá situaciones donde no sabemos cuántas iteraciones exactas necesitamos para llegar a cierto resultado. Será necesario hacer los cálculos y evaluar el resultado en cada iteración hasta que la condición que buscamos se cumpla. En esta situaciones podemos ocupar el ciclo while.
Supongamos que se nos pide calcular cuál es el número de la sucesión de Fibonacci más cercano al 400. El algoritmo para encontrar el \(n-esimo\) término de la sucesión de Fibonacci es el siguiente
\[ f_{n} = f_{n-1} + f_{n-2}\,\,\text{para } n=2,3,4,5,...\] Se definen los primeros 2 números como \(f_0=0\) y \(f_1=1\). Por tanto \(f_2\) es
\[f_{2}=f_{1}+f_{0}=1+0=1\]
Ahora vemos cómo generar la secuencia de números de Fibonacci menores a 400 usando while.
# Con n=2
fib_n_menos2 <- 0
fib_n_menos1 <- 1
fibonacci <- c(fib_n_menos2,fib_n_menos1)
fib_n <- fib_n_menos1 + fib_n_menos2
while(fib_n < 400){
fibonacci <- c(fibonacci,fib_n)
fib_n_menos2 <- fib_n_menos1
fib_n_menos1 <- fib_n
fib_n <- fib_n_menos2 + fib_n_menos1
}
print(fibonacci)## [1] 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
¿Cuál es la posición del elemento de la sucesión más cercano a 400?
## [1] 15
Un ejemplo de assign y while para guardar números de Fibonacci
Veremos una de las potenciales aplicaciones de assign para guardar variables
fib_n_menos2 <- 0
fib_n_menos1 <- 1
fib_n <- fib_n_menos1 + fib_n_menos2
n=3
while(fib_n < 400){
assign(paste0("fibonacci_",n),value = fib_n)
fib_n_menos2 <- fib_n_menos1
fib_n_menos1 <- fib_n
n <- n+1
fib_n <- fib_n_menos2 + fib_n_menos1
}
print(c(fibonacci_3,fibonacci_4,fibonacci_5,fibonacci_6,fibonacci_9,fibonacci_15))## [1] 1 2 3 5 21 377
Funciones definidas por el usuario.
Es común que hagamos ciertas operaciones de manera repetida y como consecuencia de ello tengamos que duplicar código que ya hemos ocupado con anterioridad. Lo anterior hace nuestros programas o scripts sean enormes y con mucha información redundante. Las funciones definidas por el usuarios precisamente están pensadas para que cuando tengamos que hacer una operación de manera repetida podamos llamar código con una sola instrucción sin tener que escribir todo nuevamente.
En R podemos definir nuestras propias funciones de manera fácil. Veremos cómo definir una función para pregruntar si un número entero es par o impar. Asimismo, definieremos una función para generar los primeros \(n\) números de la secuencia de Fibonacci.
Función es_par
es_par <- function(x){
# Cheamos que x es entero
if(x%%1==0){
# Checamos si es par
if(x%%2==0){
return(TRUE)
}
else
return(FALSE)
}
# Mensaje notificando que x no es entero
warning("El numero tiene que ser un numero entero")
}Probemos nuestra función
## Warning in es_par(2.2): El numero tiene que ser un numero entero
## [1] FALSE
## [1] TRUE
La operaciones vectorizadas
Dentro de las críticas más fuertes que se le hacen a R es que es un lenguaje “lento”. Una de las razones de ello, es que al ser un leguaje interpretado, R pregunta el tipo de variable (integer, floating, char, etc) en el tiempo de ejecución, sus instrucciones se traducen o interpretan una a una, cada vez que se ejecuta el programa. Cuando asignamos un valor a una variable, digamos a <- 1.0 lo que hace el interprete de R es preguntar:
- Ese número
1.0es de tipo flotante
- La variable
aes de tipo numérica - Poner un lugar en la memoria donde almacenar el
1.0 - Registar “a” como un puntero a ese lugar de memoria.
Para una explicación maravillosa sobre este tópico ver el artículo de Noam Ross
En los leguajes compliados (C, C++, fortran) declaramos los tipos de las variables desde un principio, sus instrucciones se traducen a código máquina de manera directa y optimizada mediante un compilador, lo que los hace más efecientes.
En R una forma de abordar el problema de eficiencia es utilizando operaciones vectorizadas siempre que sea posible. Las operaciones vectorizadas permiten a R llamar funciones de alto nivel que ejecutan código de C, lo que resulta en programas mucho más rápidos.
Comparemos el rendimiento de operaciones no vectorizadas vs. vectorizadas.
Operaciones no vectorizadas vs. vectorizadas.
Sea df_occs una base de datos con puntos de presencia de varias especies de reptiles y anfibios; suponga que se le pide filtar los datos de presencia por especie y contruir una lista donde cada elemento de ella es un data.frame que contiene los datos de cada especie. Veremos cómo hacer este filtrado utilizando operaciones no vectorizadas y vectorizadas. Haremos uso de la función system.time para medir el tiempo que tarde en correr los algoritmos…
Primero leemos la base de datos de los puntos de presencia y exploramos la base de datos (BD).
## [1] 4000 6
| name | longitude | latitude | prov | date | key |
|---|---|---|---|---|---|
| Sceloporus occidentalis | -120.65935 | 37.8203800000000 | gbif | 2016-03-16 | 1262381503 |
| Pseudacris regilla | -121.98533 | 40.4388299990685 | gbif | 2003-06-25 | 543583266 |
| Gehyra variegata | 137.40600 | -20.9770000000000 | gbif | 2009-04-12 | 1085956160 |
| Lampropholis delicata | 150.59459 | -34.5286200331773 | gbif | 2012-04-01 | 1074452855 |
| Zootoca vivipara | 10.36416 | 59.9032500000000 | gbif | 2015-07-31 | 1238773985 |
| Chamaeleo dilepis | 29.45000 | -23.8166700000000 | gbif | 1916-11-19 | 287041669 |
## [1] "name" "longitude" "latitude" "prov" "date" "key"
## [1] Sceloporus occidentalis Pseudacris regilla
## [3] Gehyra variegata Lampropholis delicata
## [5] Zootoca vivipara Chamaeleo dilepis
## [7] Rana pretiosa Ctenotus taeniolatus
## [9] Egernia cunninghami Anolis lemurinus
## [11] Psammodromus algirus Craugastor fleischmanni
## [13] Anaxyrus debilis Eremiascincus richardsonii
## [15] Tiliqua rugosa Eurycea bislineata
## [17] Dipsosaurus dorsalis Liolaemus bibronii
## [19] Desmognathus fuscus Notechis scutatus
## [21] Austrelaps superbus Egernia saxatilis
## [23] Anolis carolinensis Sphaerodactylus nicholsi
## [25] Rana cascadae Egernia striolata
## [27] Sceloporus undulatus Sceloporus variabilis
## [29] Stellagama stellio Lepidophyma flavimaculatum
## [31] Lacerta agilis Bassiana duperreyi
## [33] Varanus varius Pseudacris cadaverina
## [35] Ctenotus regius Lithobates pipiens
## [37] Sceloporus graciosus Amphibolurus muricatus
## [39] Xantusia vigilis Incilius alvarius
## [41] Heteronotia binoei Anaxyrus cognatus
## [43] Eleutherodactylus coqui Takydromus sexlineatus
## [45] Uta stansburiana Psammodromus hispanicus
## [47] Rhinella marina Incilius marmoreus
## [49] Incilius mazatlanensis Hemiergis decresiensis
## [51] Moloch horridus Pseudonaja textilis
## [53] Plestiodon gilberti Ctenotus uber
## [55] Liolaemus darwinii Sphaerodactylus macrolepis
## [57] Lithobates warszewitschii Platysaurus intermedius
## [59] Liolaemus koslowskyi Tiliqua nigrolutea
## [61] Anolis humilis Sceloporus malachiticus
## [63] Eremiascincus fasciolatus Liolaemus kingii
## [65] Hemiergis peronii Liolaemus quilmes
## [67] Pachymedusa dacnicolor Lithobates palmipes
## [69] Sceloporus merriami Dendrobates auratus
## [71] Anolis intermedius Liopholis inornata
## [73] Parasuta flagellum Liolaemus boulengeri
## [75] Anolis gundlachi Podarcis muralis
## [77] Liolaemus robustus Smilisca fodiens
## [79] Liolaemus tenuis Liolaemus rothi
## [81] Liolaemus kriegi Pseudemoia entrecasteauxii
## [83] Pseudemoia spenceri Protobothrops mucrosquamatus
## [85] Liolaemus fitzingerii Lobulia elegans
## [87] Liolaemus pictus Eulamprus kosciuskoi
## [89] Anolis cupreus Liolaemus elongatus
## [91] Anolis cristatellus Anaxyrus canorus
## [93] Tarentola boettgeri Anolis lionotus
## [95] Takydromus septentrionalis Hyla walkeri
## [97] Anolis tropidolepis Liolaemus petrophilus
## [99] Anolis cooki Sphaerodactylus klauberi
## [101] Liolaemus chacoensis Liolaemus multimaculatus
## [103] Phymaturus patagonicus Liolaemus scapularis
## [105] Liolaemus melanops Liolaemus olongasta
## [107] Liolaemus tari Liolaemus walkeri
## [109] Rankinia diemensis
## 109 Levels: Amphibolurus muricatus Anaxyrus canorus ... Zootoca vivipara
Algoritmo de búsqueda no vectorizado
Primero definimos la lista en donde guardaremos los data.frames de cada especie. Nótese que la lista tendrá tantos elementos como especies en la BD.
# Creamos una lista vacia
occs_df_list <- list()
# Guardamos los nombres de las especies en un vector
sp_names <- unique(occs_bd$name)Ahora implementamos el algorimo de búsqueda. La idea básica es ir recorriendo toda la columana name de la base de datos occs_bd e ir preguntando cuál registro cumple la condición occs_bd$name == sp_name_i; posteriormente almacenar estos índices en un vector llamado sp_index con los índices que cumplen la condición. Finalemente hacer un subset de occs_bd con sp_index.
# Vector donde guardare los idices (vacio)
sp_index <- c()
# Iteraremos
tiempo_novec <- system.time({
# Recorremos cada especie en sp_names
for(spi in 1:length(sp_names)){
# Especie i en la iteracion i
sp_name_i <- sp_names[spi]
# Recorremos cada registro en BD
for(registro in 1:dim(occs_bd)[1]){
# Preguntamos si coincide el nombre sp_name_i con el registro occs_bd$name (condicion 1)
if(sp_name_i == occs_bd$name[registro]){
sp_index <- c(sp_index,registro)
}
}
# Los registros que cumplieron la condicion 1
# en sp_index son ocupados para hacer el subset
# Guardamos estos en la lista
occs_df_list[[spi]] <- occs_bd[sp_index,]
# reinializamos el vector sp_index
sp_index <- c()
}
# Le ponemos nombre de la sp a cada elemento de la lista
names(occs_df_list) <- sp_names
})Cuánto tardó en correr?
## user system elapsed
## 14.897 0.132 15.139
Algoritmo de búsqueda vectorizado
Utilizaremos un comando de mi lista de estrellas, este es which
# Creamos una lista vacia
occs_df_list_vec <- list()
# nuevamente definamos una variable con los nombres de sps
sp_names <- unique(occs_bd$name)Combinamos la operación vectorizada which con el ciclo for
tiempo_vec <- system.time({
for(spi in 1:length(sp_names)){
# Especie i en la iteracion i
sp_name_i <- sp_names[spi]
sp_index_vec <- which(sp_name_i == occs_bd$name)
occs_df_list_vec[[spi]] <- occs_bd[sp_index_vec,]
}
})
names(occs_df_list_vec) <- sp_namesCuánto tardo con el vectorizado??
## user system elapsed
## 0.02200000000000202 0.00000000000000000 0.02299999999999969
Cuántas veces corrió más rápido el código vectorizado?
## [1] 658.2173913043567
La familia apply
La familia de funciones apply tienen como objetivo operar sobre estructuras de datos como matrix, data.frames, arrays, y lists de forma repetitiva. Estas funciones aplican una función sobre un conjunto de datos y devulven un arreglo el cual puede ser una matriz, una tabla de datos o una lista. Una característica de ellas, es que permiten evitar los loops, por lo que serán necesarias menos lineas de código para llegar a un mismo resultado (el código suele ser más elegante).
Entre los R-programadores se suele decir que es más R-tónico (en alusión a pythonico) utilizar las funciones de la familia apply sobre los loops.
Veamos cuales funciones conforman esta familia y sus ouputs (es información la tomé de esta página):
| Función | |
|---|---|
| base::apply | Apply Functions Over Array Margins |
| base::by | Apply a Function to a Data Frame Split by Factors |
| base::eapply | Apply a Function Over Values in an Environment |
| base::lapply | Apply a Function over a List or Vector |
| base::mapply | Apply a Function to Multiple List or Vector Arguments |
| base::rapply | Recursively Apply a Function to a List |
| base::tapply | Apply a Function Over a Ragged Array |
lapply
La función lappy regresa una lista cuyos elementos pueden ser cualquier tipo de estructura de datos (matrix, data.frames, arrays, lists, etc.).
Veremos cómo implementar nuestro algortimo de búsqueda y filtrado de datos por especie utilizando las apply functions.
Como se había mencionado anteriormente las funciones de esta gran familia aplican una función (generalmente definida por el usuario) sobre una estructura de datos y por lo tanto, para implementar nuestro algoritmo de búsqueda será necesario definir nuestra función.
# Funcion de busqueda
# sp_name: Es el nombre de la especie
# bd_datos: data.frame con los datos de presencia
# col_name: Columna en el data.frame sobre la que se hara la busqueda
find_sp <- function(sp_name, bd_datos,col_name){
# Preguntamos si coincide el nombre sp_name con el registro occs_bd[,col_name] (condicion 1)
sp_index <- which(bd_datos[,col_name] == sp_name)
# Subset con los datos que cumplen la condicion 1
sp_df <- bd_datos[sp_index,]
return(sp_df)
}Apliquemos nuestra función usando la sintaxis lapply. En esta sólo hay que especificar un vector de índices o listas (X) sobre el cual la función que definimos va a operar (find_sp).
Usando una funcón que llama c
Comparemos los rendimientos de nuestras implementaciones:
## Tiempo de ejecucion: 15.139 algoritmo no vectorizado
## Tiempo de ejecucion: 0.02299999999999969 algoritmo vectorizado
## Tiempo de ejecucion: 0.03999999999999915 algoritmo vectorizado y lappy
## Tiempo de ejecucion: 0.01099999999999923 split
purrr programación funcional
La idea es mapear una los elementos de una lista y aplicar una función. Calculemos el polígono convexo de los puntos de presencia de las especies
- Primero creamos la función para estimar los puntos que formaran el polígono convexo.
create_convex <- function(sp_db,sp_name,long,lat){
ch <- chull(sp_db[,c(long,lat)])
coords <- sp_db[c(ch, ch[1]), c(long,lat)]
coords <- cbind(sp_name,coords)
return(coords)
}- Convertimos en una lista el
data.framede la base de datos de presencia
occs_bdList <- occs_bd %>% split(.$name)
poligonos <- occs_bdList %>%
purrr::map(~create_convex(sp_db = .x,
sp_name = .x$name[1],
long = "longitude",
lat = "latitude"))Graficaremos los polígonos convexos de 5 especies tomadas al azar
pol_subm <- poligonos[sample(length(poligonos),5)]
pol_subm %>% purrr::map(~ plot(.x$longitude,.x$latitude,type="l"))## $`Liolaemus kriegi`
## NULL
##
## $`Hemiergis decresiensis`
## NULL
##
## $`Sceloporus variabilis`
## NULL
##
## $`Lobulia elegans`
## NULL
##
## $`Zootoca vivipara`
## NULL
Pauqetes usados durante este tutorial
## R version 3.5.1 (2018-07-02)
## Platform: x86_64-apple-darwin15.6.0 (64-bit)
## Running under: macOS High Sierra 10.13.6
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRlapack.dylib
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] XLConnect_0.2-15 XLConnectJars_0.2-15 gdata_2.18.0
## [4] knitr_1.21 dygraphs_1.1.1.6
##
## loaded via a namespace (and not attached):
## [1] Rcpp_1.0.0 compiler_3.5.1 later_0.7.5 questionr_0.7.0
## [5] highr_0.7 rmdformats_0.3.3 tools_3.5.1 xts_0.10-2
## [9] digest_0.6.18 jsonlite_1.6 evaluate_0.12 lattice_0.20-35
## [13] rlang_0.3.1 shiny_1.2.0 rstudioapi_0.7 yaml_2.2.0
## [17] xfun_0.4 rJava_0.9-10 stringr_1.3.1 htmlwidgets_1.2
## [21] gtools_3.8.1 grid_3.5.1 R6_2.3.0 rmarkdown_1.11
## [25] bookdown_0.7 purrr_0.2.5 magrittr_1.5 promises_1.0.1
## [29] htmltools_0.3.6 mime_0.6 xtable_1.8-3 httpuv_1.4.5
## [33] stringi_1.2.4 miniUI_0.1.1.1 zoo_1.8-3